#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <mmsystem.h>
#include <d3d9.h>
#include <d3dx9.h>

#include "Constant.h"
#include "Entity.h"
#include "EntityGenerator.h"
#include "ForceGenerator.hpp"
#include "SimulationManager.h"
#include "Vector3.hpp"


//DirectX globals
HWND                    g_hWnd          = 0;
LPDIRECT3D9             g_pD3D          = 0;
LPDIRECT3DDEVICE9       g_pd3dDevice    = 0;
LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer = 0;
LPDIRECT3DTEXTURE9      g_pTexture      = 0;

//DirectX structs
struct Vertex
{
    D3DXVECTOR3 position;
    D3DCOLOR    color;

	enum FVF
	{
		FVF_Flags						= D3DFVF_XYZ|D3DFVF_DIFFUSE
	};
};

//Engine globals
EntityGenerator*		generator		= 0;
SimulationManager*		manager			= 0;
double					startTime		= 0.0f;
float					elapsedTime		= 0.0f;

//Function prototypes
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

void InitDisplay(void);
void InitEngine(void);
void Update(void);
void Render(void);
void Release(void);

//Function helpers
DWORD FtoDW(FLOAT f) 
{ 
	return *((DWORD*)&f); 
}
D3DXVECTOR3* VtoDXV(Vector3* source)
{
	return new D3DXVECTOR3(source->x, source->y, source->z);
}

//Application entry point
int WINAPI WinMain(	HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
	//Register window
	WNDCLASSEX winClass; 
	MSG        uMsg;
    memset(&uMsg,0,sizeof(uMsg));
    
	winClass.lpszClassName = "Physic_Engine";
	winClass.cbSize        = sizeof(WNDCLASSEX);
	winClass.style         = CS_HREDRAW | CS_VREDRAW;
	winClass.lpfnWndProc   = WindowProc;
	winClass.hInstance     = hInstance;
	winClass.hIcon	       = 0;
    winClass.hIconSm	   = 0;
	winClass.hCursor       = 0;
	winClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	winClass.lpszMenuName  = NULL;
	winClass.cbClsExtra    = 0;
	winClass.cbWndExtra    = 0;

	if(!RegisterClassEx(&winClass)) 
		return E_FAIL;

	g_hWnd = CreateWindowEx( NULL, "Physic_Engine", "Physic - Particle Point Sprites", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 1024, 768, NULL, NULL, hInstance, NULL );

	if( g_hWnd == NULL ) return E_FAIL;

    ShowWindow( g_hWnd, nCmdShow );
    UpdateWindow( g_hWnd );

	//Setup directx and engine
	InitDisplay();
	InitEngine();

	//Main loop
	while(uMsg.message != WM_QUIT )
	{
		startTime = timeGetTime();
		if(PeekMessage( &uMsg, NULL, 0, 0, PM_REMOVE ) )
		{ 
			TranslateMessage( &uMsg );
			DispatchMessage( &uMsg );
		}
        else
		{
			//Process update
			Update();
			//Render update
		    Render();
		}
		elapsedTime = (float)((timeGetTime() - startTime) * 0.001);
		if (elapsedTime < 0)
		{
			elapsedTime = 0;
		}
	}

	Release();

    UnregisterClass("Physic_Engine", winClass.hInstance);

	return uMsg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch( msg )
	{
        case WM_KEYDOWN:
			switch(wParam)
			{
				case VK_ESCAPE:
					PostQuitMessage(0);
					break;
			}
			break;

		case WM_CLOSE:
			PostQuitMessage(0);	
			break;
		
        case WM_DESTROY:
            PostQuitMessage(0);
			break;

		default:
			return DefWindowProc( hWnd, msg, wParam, lParam );
			break;
	}

	return 0;
}

void InitDisplay()
{
	//Setup device
	g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);

    D3DDISPLAYMODE d3ddm;
    g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));

    d3dpp.Windowed               = TRUE;
    d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat       = d3ddm.Format;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
    d3dpp.PresentationInterval   = D3DPRESENT_INTERVAL_IMMEDIATE;

    g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,
                          D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                          &d3dpp, &g_pd3dDevice );
	
	//Setup field of view
	D3DXMATRIX matView;
    D3DXMatrixLookAtLH( &matView, &D3DXVECTOR3(0.0f, 0.0f,-10.0f), 
		                          &D3DXVECTOR3(0.0f, 0.0f, 0.0f), 
		                          &D3DXVECTOR3(0.0f, 1.0f, 0.0f ) );
    g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

    D3DXMATRIX matProj;
    D3DXMatrixPerspectiveFovLH( &matProj, D3DXToRadian( 45.0f ), 1.0f, 1.0f, 200.0f );
    g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
    g_pd3dDevice->SetRenderState( D3DRS_LIGHTING,  FALSE );

	//Load texture
	D3DXCreateTextureFromFile(g_pd3dDevice, "particle.bmp", &g_pTexture);

	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

    g_pd3dDevice->CreateVertexBuffer(2048 * sizeof(Vertex), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY | D3DUSAGE_POINTS, Vertex::FVF_Flags, D3DPOOL_DEFAULT, &g_pVertexBuffer, NULL);
}

void InitEngine()
{
	//Show different cases of the engine
	manager = new SimulationManager();
	manager->DefineConstraint(50.0f);
	manager->DefineConstraint(new Vector3(-10, -10, -10), new Vector3(10, 10, 10));
	
	//Random Number
	generator = new EntityGenerator();
	generator->SetDefaultMass(2.0f);
	generator->SetRangePosition(X, 0, 0);
	generator->SetRangePosition(Y, 0, 0);
	generator->SetRangePosition(Z, 0, 0);
	generator->SetRangeForce(X, -5.0f, 5.0f);
	generator->SetRangeForce(Y, 7, 9);
	generator->SetRangeForce(Z, 0, 0);
	generator->Generate(*manager->GetEntityList(), 1000);
	manager->RegisterAllEntities(GRAVITY);	
	//manager->RegisterAllEntities(DRAG);
	/*
	//Time Constraint
	manager->DefineConstraint(3.0f);
	generator = new EntityGenerator();
	generator->SetDefaultMass(1.0f);
	generator->SetRangePosition(X, 3, 6);
	generator->SetRangePosition(Y, 3, 6);
	generator->SetRangePosition(Z, 0, 0);
	generator->SetRangeForce(X, -1, -2);
	generator->SetRangeForce(Y, -1, -2);
	generator->SetRangeForce(Z, 0, 0);
	generator->Generate(*manager->GetEntityList(), 10);
	*/
	
	
	//Gravity
	//manager->AddEntity(0, 1.0f, new Vector3(0.0f, -9.0f, 0.0f), new Vector3(0.0f, 2.0f, 0.0f));
	/*manager->AddEntity(1, 1.0f, new Vector3(-9.0f, -8.0f, 0.0f), new Vector3(2.0f, 2.0f, 0.0f));
	manager->AddEntity(2, 1.0f, new Vector3(-9.0f, -7.0f, 0.0f), new Vector3(2.0f, 2.0f, 0.0f));
	manager->AddEntity(3, 1.0f, new Vector3(-9.0f, -6.0f, 0.0f), new Vector3(2.0f, 2.0f, 0.0f));*/
	//manager->RegisterEntity(GRAVITY, 0);
	//manager->RegisterEntity(GRAVITY, 3);
	//manager->RegisterAllEntities(GRAVITY);	

	/*
	//Drag
	manager->AddEntity(0, 1.0f, new Vector3(-9.0f, 1.0f, 0.0f), new Vector3(2.0f, 0.0f, 0.0f));
	manager->AddEntity(1, 1.0f, new Vector3(-9.0f, -1.0f, 0.0f), new Vector3(2.0f, 0.0f, 0.0f));
	manager->RegisterEntity(DRAG, 0);
	*/

	//Spring
	/*manager->DefineConstraint(20000.0f);
	manager->AddEntity(0, 1.0f, new Vector3(-3.0f, 1.0f, 0.0f), new Vector3(0.0f, 0.0f, 0.0f));
	manager->AddEntity(1, 1.0f, new Vector3(-3.0f, -1.0f, 0.0f), new Vector3(0.0f, 0.0f, 0.0f));
	manager->AddEntity(2, 1.0f, new Vector3(3.0f, 3.0f, 0.0f), new Vector3(0.0f, 0.0f, 0.0f));
	manager->AddEntity(3, 1.0f, new Vector3(3.0f, -3.0f, 0.0f), new Vector3(0.0f, 0.0f, 0.0f));;
	manager->AddEntity(4, 1.0f, new Vector3(-4.0f, 3.0f, 0.0f), new Vector3(0.0f, 0.0f, 0.0f));
	manager->AddEntity(5, 1.0f, new Vector3(0.0f, 3.0f, 0.0f), new Vector3(0.0f, 0.0f, 0.0f));;
	manager->RegisterSpring(0, 1);
	manager->RegisterSpring(2, 3);
	manager->RegisterSpring(4, 5);*/
}

void Update()
{
	manager->Update(elapsedTime);
}

void Render()
{
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_COLORVALUE(0.0f,0.0f,0.0f,1.0f), 1.0f, 0);

	D3DXMATRIX matWorld;
    D3DXMatrixTranslation(&matWorld, 0.0f, 0.0f, 20.0f);
    g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);

    g_pd3dDevice->BeginScene();
	g_pd3dDevice->SetTexture(0, g_pTexture);

	//Set render states for point sprites
    g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
    g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
    g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
    g_pd3dDevice->SetRenderState(D3DRS_POINTSPRITEENABLE, TRUE);    
    g_pd3dDevice->SetRenderState(D3DRS_POINTSCALEENABLE,  TRUE);    
    g_pd3dDevice->SetRenderState(D3DRS_POINTSIZE,     FtoDW(1.0));  
    g_pd3dDevice->SetRenderState(D3DRS_POINTSIZE_MIN, FtoDW(1.0f)); 
    g_pd3dDevice->SetRenderState(D3DRS_POINTSCALE_A,  FtoDW(0.0f)); 
    g_pd3dDevice->SetRenderState(D3DRS_POINTSCALE_B,  FtoDW(0.0f)); 
    g_pd3dDevice->SetRenderState(D3DRS_POINTSCALE_C,  FtoDW(1.0f)); 

	Vertex *vertices;
	g_pVertexBuffer->Lock(0, manager->Count() * sizeof(Vertex), (void**)&vertices, D3DLOCK_DISCARD);

	map<int, Entity*>* entities = manager->GetEntityList();
	map<int, Entity*>::iterator cur = entities->begin();
	map<int, Entity*>::iterator end = entities->end();

	//Render all sprites in the engine
	while (cur != end)
	{
		vertices->position = *VtoDXV(cur->second->Position());
		vertices->color = D3DCOLOR_COLORVALUE(1.0f, 1.0f, 1.0f, 1.0f);
		vertices++;
		cur++;
	}
    g_pVertexBuffer->Unlock();
	
    g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(Vertex) );
    g_pd3dDevice->SetFVF(Vertex::FVF_Flags);
	g_pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, 0, manager->Count());

	//Reset render states
    g_pd3dDevice->SetRenderState(D3DRS_POINTSPRITEENABLE, FALSE);
    g_pd3dDevice->SetRenderState(D3DRS_POINTSCALEENABLE,  FALSE);
    g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
    g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);

	g_pd3dDevice->EndScene();
    g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
}

void Release()
{
	if(g_pVertexBuffer != NULL)
	{
		g_pVertexBuffer->Release();
	}

    if(g_pTexture != NULL)
	{
		g_pTexture->Release();
	}

    if(g_pd3dDevice != NULL)
	{
		g_pd3dDevice->Release();
	}

    if(g_pD3D != NULL)
	{
        g_pD3D->Release();
	}
}